#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
文档内容搜索工具 (GUI版本)
功能：在指定目录下搜索包含关键字的文件，支持多种文档格式，带GUI界面和查询日志显示
新增功能：文件名替换模式 - 可以指定目录和替换的文件名字符串
"""
import os
import sys
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
import threading
import queue
import logging
import datetime
import csv
import subprocess

# 导入文档处理相关库
try:
    from PyPDF2 import PdfReader
    has_pypdf2 = True
except ImportError:
    has_pypdf2 = False

try:
    import openpyxl
    has_openpyxl = True
except ImportError:
    has_openpyxl = False

try:
    import python_pptx as pptx
    has_pptx = True
except ImportError:
    has_pptx = False

try:
    from docx import Document
    has_docx = True
except ImportError:
    has_docx = False


class DocSearchGUI:
    """文档内容搜索工具的GUI版本"""
    def __init__(self, root):
        self.root = root
        self.root.title("RZT验收文档处理工具")
        self.root.geometry("1000x700")
        self.root.minsize(800, 600)
        
        # 设置字体以支持中文
        self.default_font = ('SimHei', 10)
        self.header_font = ('SimHei', 12, 'bold')
        
        # 搜索参数
        self.directory = ""
        self.keyword = ""
        self.search_cancelled = False
        self.search_thread = None
        self.result_queue = queue.Queue()
        self.search_results = []
        
        # 替换参数
        self.replace_directory = ""
        self.find_text = ""
        self.replace_text = ""
        self.replace_cancelled = False
        self.replace_thread = None
        self.replace_file_types = ['.docx']  # 默认只支持docx
        
        # 文件名替换参数
        self.filename_replace_directory = ""
        self.filename_find_text = ""
        self.filename_replace_text = ""
        self.filename_replace_cancelled = False
        self.filename_replace_thread = None
        
        # 日志系统
        self.setup_logger()
        
        # 创建UI
        self.create_ui()
        
        # 绑定事件
        self.bind_events()
        
    def setup_logger(self):
        """配置日志系统"""
        # 创建日志文本区域
        self.log_text = scrolledtext.ScrolledText(
            self.root, wrap=tk.WORD, font=self.default_font,
            bg='#f0f0f0', fg='#333333', height=5
        )
        
        # 创建自定义日志处理器
        class TextHandler(logging.Handler):
            def __init__(self, text_widget):
                super().__init__()
                self.text_widget = text_widget
                
            def emit(self, record):
                msg = self.format(record)
                def append_log():
                    self.text_widget.config(state=tk.NORMAL)
                    self.text_widget.insert(tk.END, msg + '\n')
                    self.text_widget.see(tk.END)
                    self.text_widget.config(state=tk.DISABLED)
                
                # 在主线程中更新UI
                if self.text_widget.winfo_exists():
                    self.text_widget.after(0, append_log)
        
        # 配置logger
        self.logger = logging.getLogger('DocSearchGUI')
        self.logger.setLevel(logging.INFO)
        
        # 清除已有的处理器
        for handler in self.logger.handlers[:]:
            self.logger.removeHandler(handler)
            handler.close()
        
        # 创建文本处理器
        text_handler = TextHandler(self.log_text)
        text_handler.setLevel(logging.INFO)
        formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s', 
                                     datefmt='%H:%M:%S')
        text_handler.setFormatter(formatter)
        self.logger.addHandler(text_handler)
        
        # 创建文件处理器
        log_file = f"docsearch_gui_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
        file_handler = logging.FileHandler(log_file, encoding='utf-8')
        file_handler.setLevel(logging.DEBUG)
        file_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
        self.logger.addHandler(file_handler)
    
    def create_ui(self):
        """创建用户界面"""
        # 创建主框架
        main_frame = ttk.Frame(self.root, padding="10")
        main_frame.pack(fill=tk.BOTH, expand=True)
        
        # 创建模式选择器
        mode_frame = ttk.Frame(main_frame)
        mode_frame.pack(fill=tk.X, pady=(0, 5))
        
        ttk.Label(mode_frame, text="工作模式:", font=self.default_font).pack(side=tk.LEFT)
        self.mode_var = tk.StringVar(value="search")
        mode_frame.columnconfigure(1, weight=1)
        
        # 创建搜索参数区域
        self.search_frame = ttk.LabelFrame(main_frame, text="搜索参数", padding="10")
        self.search_frame.pack(fill=tk.X, pady=(0, 10))
        
        # 目录选择
        ttk.Label(self.search_frame, text="搜索目录:", font=self.default_font).grid(row=0, column=0, sticky=tk.W, pady=5)
        self.directory_var = tk.StringVar()
        ttk.Entry(self.search_frame, textvariable=self.directory_var, width=50, font=self.default_font).grid(row=0, column=1, sticky=tk.EW, pady=5, padx=(5, 10))
        ttk.Button(self.search_frame, text="浏览...", command=self.browse_directory, width=10).grid(row=0, column=2, pady=5)
        
        # 关键词输入
        ttk.Label(self.search_frame, text="搜索关键字:", font=self.default_font).grid(row=1, column=0, sticky=tk.W, pady=5)
        self.keyword_var = tk.StringVar()
        ttk.Entry(self.search_frame, textvariable=self.keyword_var, width=50, font=self.default_font).grid(row=1, column=1, sticky=tk.EW, pady=5, padx=(5, 10))
        
        # 搜索和取消按钮
        button_frame = ttk.Frame(self.search_frame)
        button_frame.grid(row=2, column=1, sticky=tk.E, pady=10)
        
        self.search_button = ttk.Button(button_frame, text="开始搜索", command=self.start_search, width=12)
        self.search_button.pack(side=tk.LEFT, padx=(0, 10))
        
        self.cancel_button = ttk.Button(button_frame, text="取消搜索", command=self.cancel_search, width=12, state=tk.DISABLED)
        self.cancel_button.pack(side=tk.LEFT)
        
        # 配置列权重，使输入框可以拉伸
        self.search_frame.columnconfigure(1, weight=1)
        
        # 创建RESULT区域
        self.results_frame = ttk.LabelFrame(main_frame, text="搜索结果", padding="10")
        self.results_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
        
        # 排序选择器
        sort_frame = ttk.Frame(self.results_frame)
        sort_frame.pack(fill=tk.X, pady=(0, 5))
        
        ttk.Label(sort_frame, text="排序方式:", font=self.default_font).pack(side=tk.LEFT)
        self.sort_var = tk.StringVar(value="文件名")
        sort_combo = ttk.Combobox(
            sort_frame, textvariable=self.sort_var, state="readonly",
            values=["文件名", "文件大小", "修改时间"], width=10, font=self.default_font
        )
        sort_combo.pack(side=tk.LEFT, padx=(5, 15))
        sort_combo.bind("<<ComboboxSelected>>", lambda event: self.sort_results())
        
        # 导出按钮
        self.export_button = ttk.Button(sort_frame, text="导出结果", command=self.ask_export_format, width=10, state=tk.DISABLED)
        self.export_button.pack(side=tk.RIGHT)
        
        # 结果列表
        columns = ("name", "path", "size", "modified", "match_type")
        self.result_tree = ttk.Treeview(
            self.results_frame, columns=columns, show="headings", selectmode="extended"
        )
        
        # 定义列宽和标题
        self.result_tree.column("name", width=200, anchor=tk.W)
        self.result_tree.column("path", width=400, anchor=tk.W)
        self.result_tree.column("size", width=100, anchor=tk.E)
        self.result_tree.column("modified", width=150, anchor=tk.CENTER)
        self.result_tree.column("match_type", width=100, anchor=tk.CENTER)
        
        self.result_tree.heading("name", text="文件名")
        self.result_tree.heading("path", text="文件路径")
        self.result_tree.heading("size", text="大小")
        self.result_tree.heading("modified", text="修改时间")
        self.result_tree.heading("match_type", text="匹配类型")
        
        # 添加滚动条
        yscroll = ttk.Scrollbar(self.results_frame, orient=tk.VERTICAL, command=self.result_tree.yview)
        xscroll = ttk.Scrollbar(self.results_frame, orient=tk.HORIZONTAL, command=self.result_tree.xview)
        self.result_tree.configure(yscroll=yscroll.set, xscroll=xscroll.set)
        
        # 放置树状视图和滚动条
        self.result_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        yscroll.pack(side=tk.RIGHT, fill=tk.Y)
        xscroll.pack(side=tk.BOTTOM, fill=tk.X)
        
        # 创建替换参数区域（默认隐藏）
        self.replace_frame = ttk.LabelFrame(main_frame, text="替换参数", padding="10")
        self.replace_frame.columnconfigure(1, weight=1)  # 确保替换区域也可以拉伸
        
        # 创建文件名替换参数区域（默认隐藏）
        self.filename_replace_frame = ttk.LabelFrame(main_frame, text="文件名替换参数", padding="10")
        self.filename_replace_frame.columnconfigure(1, weight=1)  # 确保区域可以拉伸
        
        # 文件名替换目录选择
        ttk.Label(self.filename_replace_frame, text="替换目录:", font=self.default_font).grid(row=0, column=0, sticky=tk.W, pady=5)
        self.filename_replace_directory_var = tk.StringVar()
        ttk.Entry(self.filename_replace_frame, textvariable=self.filename_replace_directory_var, width=50, font=self.default_font).grid(row=0, column=1, sticky=tk.EW, pady=5, padx=(5, 10))
        ttk.Button(self.filename_replace_frame, text="浏览...", command=self.browse_filename_replace_directory, width=10).grid(row=0, column=2, pady=5)
        
        # 查找文本输入
        ttk.Label(self.filename_replace_frame, text="查找文本:", font=self.default_font).grid(row=1, column=0, sticky=tk.W, pady=5)
        self.filename_find_text_var = tk.StringVar()
        ttk.Entry(self.filename_replace_frame, textvariable=self.filename_find_text_var, width=50, font=self.default_font).grid(row=1, column=1, sticky=tk.EW, pady=5, padx=(5, 10))
        
        # 替换文本输入
        ttk.Label(self.filename_replace_frame, text="替换为:", font=self.default_font).grid(row=2, column=0, sticky=tk.W, pady=5)
        self.filename_replace_text_var = tk.StringVar()
        ttk.Entry(self.filename_replace_frame, textvariable=self.filename_replace_text_var, width=50, font=self.default_font).grid(row=2, column=1, sticky=tk.EW, pady=5, padx=(5, 10))
        
        # 替换和取消按钮
        filename_replace_button_frame = ttk.Frame(self.filename_replace_frame)
        filename_replace_button_frame.grid(row=3, column=1, sticky=tk.E, pady=10)
        
        self.filename_replace_button = ttk.Button(filename_replace_button_frame, text="开始替换", command=self.start_filename_replace, width=12)
        self.filename_replace_button.pack(side=tk.LEFT, padx=(0, 10))
        
        self.filename_replace_cancel_button = ttk.Button(filename_replace_button_frame, text="取消替换", command=self.cancel_filename_replace, width=12, state=tk.DISABLED)
        self.filename_replace_cancel_button.pack(side=tk.LEFT)
        
        # 目录选择
        ttk.Label(self.replace_frame, text="替换目录:", font=self.default_font).grid(row=0, column=0, sticky=tk.W, pady=5)
        self.replace_directory_var = tk.StringVar()
        ttk.Entry(self.replace_frame, textvariable=self.replace_directory_var, width=50, font=self.default_font).grid(row=0, column=1, sticky=tk.EW, pady=5, padx=(5, 10))
        ttk.Button(self.replace_frame, text="浏览...", command=self.browse_replace_directory, width=10).grid(row=0, column=2, pady=5)
        
        # 文件类型选择
        ttk.Label(self.replace_frame, text="文件类型:", font=self.default_font).grid(row=1, column=0, sticky=tk.W, pady=5)
        self.file_type_var = tk.StringVar(value=".docx")
        file_type_combo = ttk.Combobox(
            self.replace_frame, textvariable=self.file_type_var, state="readonly",
            values=[".docx"], width=10, font=self.default_font
        )
        file_type_combo.grid(row=1, column=1, sticky=tk.W, pady=5, padx=(5, 10))
        file_type_combo.bind("<<ComboboxSelected>>", lambda event: self.update_file_types())
        
        # 查找文本输入
        ttk.Label(self.replace_frame, text="查找文本:", font=self.default_font).grid(row=2, column=0, sticky=tk.W, pady=5)
        self.find_text_var = tk.StringVar()
        ttk.Entry(self.replace_frame, textvariable=self.find_text_var, width=50, font=self.default_font).grid(row=2, column=1, sticky=tk.EW, pady=5, padx=(5, 10))
        
        # 替换文本输入
        ttk.Label(self.replace_frame, text="替换为:", font=self.default_font).grid(row=3, column=0, sticky=tk.W, pady=5)
        self.replace_text_var = tk.StringVar()
        ttk.Entry(self.replace_frame, textvariable=self.replace_text_var, width=50, font=self.default_font).grid(row=3, column=1, sticky=tk.EW, pady=5, padx=(5, 10))
        
        # 替换和取消按钮
        replace_button_frame = ttk.Frame(self.replace_frame)
        replace_button_frame.grid(row=4, column=1, sticky=tk.E, pady=10)
        
        self.replace_button = ttk.Button(replace_button_frame, text="开始替换", command=self.start_replace, width=12)
        self.replace_button.pack(side=tk.LEFT, padx=(0, 10))
        
        self.replace_cancel_button = ttk.Button(replace_button_frame, text="取消替换", command=self.cancel_replace, width=12, state=tk.DISABLED)
        self.replace_cancel_button.pack(side=tk.LEFT)
        
        # 模式选择按钮组
        mode_button_frame = ttk.Frame(mode_frame)
        mode_button_frame.pack(side=tk.LEFT, padx=10)
        
        ttk.Radiobutton(mode_button_frame, text="查找模式", variable=self.mode_var, value="search", command=self.switch_mode, width=10).pack(side=tk.LEFT)
        ttk.Radiobutton(mode_button_frame, text="替换模式", variable=self.mode_var, value="replace", command=self.switch_mode, width=10).pack(side=tk.LEFT)
        ttk.Radiobutton(mode_button_frame, text="文件名替换", variable=self.mode_var, value="filename_replace", command=self.switch_mode, width=12).pack(side=tk.LEFT)
        
        # 创建右键菜单
        self.context_menu = tk.Menu(self.root, tearoff=0)
        self.context_menu.add_command(label="打开文件位置", command=self.open_file_location)
        
        # 创建日志显示区域
        log_frame = ttk.LabelFrame(main_frame, text="查询日志", padding="10")
        log_frame.pack(fill=tk.X, pady=(0, 10))
        
        # 放置日志文本区域
        self.log_text.pack(fill=tk.X, expand=False)
        self.log_text.config(state=tk.DISABLED)
        
        # 搜索统计信息
        status_frame = ttk.Frame(main_frame)
        status_frame.pack(fill=tk.X, side=tk.BOTTOM)
        
        self.status_var = tk.StringVar(value="就绪")
        status_label = ttk.Label(status_frame, textvariable=self.status_var, font=self.default_font, anchor=tk.W)
        status_label.pack(fill=tk.X, padx=10, pady=5)
    
    def switch_mode(self):
        """切换查找模式、替换模式和文件名替换模式"""
        self.mode = self.mode_var.get()
        
        if self.mode == "search":
            self.search_frame.pack(fill=tk.X, pady=(0, 10))
            self.replace_frame.pack_forget()
            self.filename_replace_frame.pack_forget()
            self.results_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
            self.status_var.set("就绪（查找模式）")
        elif self.mode == "replace":
            self.search_frame.pack_forget()
            self.replace_frame.pack(fill=tk.X, pady=(0, 10))
            self.filename_replace_frame.pack_forget()
            self.results_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
            self.status_var.set("就绪（替换模式）")
        else:
            self.search_frame.pack_forget()
            self.replace_frame.pack_forget()
            self.filename_replace_frame.pack(fill=tk.X, pady=(0, 10))
            self.results_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
            self.status_var.set("就绪（文件名替换模式）")
    
    def update_file_types(self):
        """更新要替换的文件类型"""
        file_type = self.file_type_var.get()
        self.replace_file_types = [file_type]
        self.logger.debug(f"已选择文件类型: {file_type}")
    
    def bind_events(self):
        """绑定事件处理"""
        # 右键菜单事件
        self.result_tree.bind("<Button-3>", self.show_context_menu)
        
        # 双击打开文件位置
        self.result_tree.bind("<Double-1>", lambda event: self.open_file_location())
        
        # 处理队列消息的定时器
        self.process_queue()
    
    
    def browse_directory(self):
        """浏览并选择目录"""
        directory = filedialog.askdirectory(title="选择搜索目录")
        if directory:
            self.directory_var.set(directory)
            self.directory = directory
    
    def browse_replace_directory(self):
        """浏览并选择替换目录"""
        directory = filedialog.askdirectory(title="选择替换目录")
        if directory:
            self.replace_directory_var.set(directory)
            self.replace_directory = directory
    
    def browse_filename_replace_directory(self):
        """浏览并选择文件名替换目录"""
        directory = filedialog.askdirectory(title="选择文件名替换目录")
        if directory:
            self.filename_replace_directory_var.set(directory)
            self.filename_replace_directory = directory
    
    def start_search(self):
        """开始搜索过程"""
        # 获取搜索参数
        self.directory = self.directory_var.get()
        self.keyword = self.keyword_var.get()
        
        # 验证参数
        if not self.directory or not os.path.isdir(self.directory):
            messagebox.showerror("错误", "请选择有效的搜索目录")
            return
        
        if not self.keyword:
            messagebox.showerror("错误", "请输入搜索关键字")
            return
        
        # 重置搜索状态
        self.search_cancelled = False
        self.search_results = []
        
        # 清空结果列表
        for item in self.result_tree.get_children():
            self.result_tree.delete(item)
        
        # 更新UI状态
        self.search_button.config(state=tk.DISABLED)
        self.cancel_button.config(state=tk.NORMAL)
        self.export_button.config(state=tk.DISABLED)
        
        # 记录开始信息
        self.logger.info(f"开始搜索 - 目录: {self.directory}, 关键字: {self.keyword}")
        self.status_var.set("正在初始化搜索...")
        
        # 创建并启动搜索线程
        self.search_thread = threading.Thread(target=self.search_in_thread)
        self.search_thread.daemon = True
        self.search_thread.start()
    
    def search_in_thread(self):
        """在后台线程中执行搜索"""
        try:
            # 发送初始状态
            self.result_queue.put(('status', "正在计算文件总数..."))
            
            # 计算总文件数
            total_files = 0
            for root, _, files in os.walk(self.directory):
                total_files += len(files)
                if self.search_cancelled:
                    self.result_queue.put(('cancelled', None))
                    return
            
            self.logger.debug(f"总共找到 {total_files} 个文件")
            
            # 搜索文件
            processed_files = 0
            found_count = 0
            
            for root, _, files in os.walk(self.directory):
                if self.search_cancelled:
                    self.result_queue.put(('cancelled', None))
                    return
                    
                for file in files:
                    if self.search_cancelled:
                        self.result_queue.put(('cancelled', None))
                        return
                        
                    processed_files += 1
                    file_path = os.path.join(root, file)
                    
                    # 发送状态更新
                    progress = f"{processed_files}/{total_files} ({processed_files/total_files:.0%})"
                    self.result_queue.put(('status', f"搜索中... {progress}"))
                    
                    # 检查文件名
                    if self.keyword.lower() in file.lower():
                        try:
                            file_size = os.path.getsize(file_path)
                            modified_time = os.path.getmtime(file_path)
                            result = {
                                'name': file,
                                'path': file_path,
                                'size': file_size,
                                'modified_time': modified_time,
                                'match_type': '文件名'
                            }
                            self.search_results.append(result)
                            found_count += 1
                            self.result_queue.put(('result', result))
                            self.logger.debug(f"找到匹配文件名: {file_path}")
                        except Exception as e:
                            error_msg = f"无法读取文件 {file_path}: {str(e)}"
                            self.result_queue.put(('error', error_msg))
                            self.logger.warning(error_msg)
                        continue
                        
                    # 检查文件内容
                    try:
                        content = self.extract_text(file_path)
                        if content and self.keyword.lower() in content.lower():
                            file_size = os.path.getsize(file_path)
                            modified_time = os.path.getmtime(file_path)
                            result = {
                                'name': file,
                                'path': file_path,
                                'size': file_size,
                                'modified_time': modified_time,
                                'match_type': '文件内容'
                            }
                            self.search_results.append(result)
                            found_count += 1
                            self.result_queue.put(('result', result))
                            self.logger.debug(f"找到匹配内容: {file_path}")
                    except Exception as e:
                        error_msg = f"无法读取文件 {file_path}: {str(e)}"
                        self.result_queue.put(('error', error_msg))
                        self.logger.warning(error_msg)
            
            # 搜索完成
            self.result_queue.put(('finished', (found_count, processed_files)))
        except Exception as e:
            self.logger.error(f"搜索过程中发生错误: {str(e)}")
            self.result_queue.put(('error', str(e)))
    
    def extract_text(self, file_path):
        """根据文件类型提取文本内容"""
        _, ext = os.path.splitext(file_path)
        ext = ext.lower()
        
        content = ""
        
        try:
            if ext == '.txt':
                try:
                    with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
                        content = f.read()
                except Exception as e:
                    error_msg = f"读取txt文件时出错 ({file_path}): {str(e)}"
                    self.logger.warning(error_msg)
                    content = ""
            elif ext == '.docx' and has_docx:
                try:
                    doc = Document(file_path)
                    # 提取段落文本
                    content = "\n".join([para.text for para in doc.paragraphs]) + "\n"
                    # 提取表格文本
                    for table in doc.tables:
                        for row in table.rows:
                            for cell in row.cells:
                                for para in cell.paragraphs:
                                    content += para.text + "\n"
                except Exception as e:
                    # 处理docx文件读取错误，特别是'word/NULL'错误
                    error_msg = f"读取docx文件时出错 ({file_path}): {str(e)}"
                    self.logger.warning(error_msg)
                    content = ""  # 返回空字符串继续处理其他文件
            elif ext == '.pdf' and has_pypdf2:
                try:
                    with open(file_path, 'rb') as f:
                        reader = PdfReader(f)
                        for page in reader.pages:
                            page_text = page.extract_text() or ""
                            content += page_text + "\n"
                except Exception as e:
                    error_msg = f"读取pdf文件时出错 ({file_path}): {str(e)}"
                    self.logger.warning(error_msg)
                    content = ""
            elif ext == '.xlsx' and has_openpyxl:
                try:
                    wb = openpyxl.load_workbook(file_path, read_only=True)
                    for sheet_name in wb.sheetnames:
                        sheet = wb[sheet_name]
                        for row in sheet.iter_rows():
                            for cell in row:
                                if cell.value:
                                    content += str(cell.value) + "\t"
                            content += "\n"
                except Exception as e:
                    error_msg = f"读取xlsx文件时出错 ({file_path}): {str(e)}"
                    self.logger.warning(error_msg)
                    content = ""
            elif ext == '.pptx' and has_pptx:
                try:
                    prs = pptx.Presentation(file_path)
                    for slide in prs.slides:
                        for shape in slide.shapes:
                            if hasattr(shape, 'text'):
                                content += shape.text + "\n"
                except Exception as e:
                    error_msg = f"读取pptx文件时出错 ({file_path}): {str(e)}"
                    self.logger.warning(error_msg)
                    content = ""
        except Exception as e:
            # 捕获其他所有未预期的错误
            error_msg = f"处理文件时发生未知错误 ({file_path}): {str(e)}"
            self.logger.error(error_msg)
            content = ""
            
        return content
    
    def process_queue(self):
        """处理队列中的消息"""
        try:
            # 非阻塞方式获取队列中的所有消息
            while True:
                try:
                    message_type, data = self.result_queue.get_nowait()
                    
                    if message_type == 'status':
                        # 更新状态
                        self.status_var.set(data)
                    elif message_type == 'result':
                        # 添加搜索结果到列表
                        self.add_result_to_tree(data)
                    elif message_type == 'error':
                        # 显示错误信息
                        self.logger.warning(data)
                    elif message_type == 'finished':
                        # 根据当前模式处理完成消息
                        found_count, processed_files = data
                        
                        if self.mode == 'search':
                            # 搜索完成
                            self.logger.info(f"搜索完成。共处理 {processed_files} 个文件，找到 {found_count} 个匹配文件。")
                            self.status_var.set(f"搜索完成。共处理 {processed_files} 个文件，找到 {found_count} 个匹配文件。")
                            
                            # 更新UI状态
                            self.search_button.config(state=tk.NORMAL)
                            self.cancel_button.config(state=tk.DISABLED)
                        elif self.mode == 'replace':
                            # 内容替换完成
                            self.logger.info(f"替换完成。共处理 {processed_files} 个文件，成功替换 {found_count} 个文件。")
                            self.status_var.set(f"替换完成。共处理 {processed_files} 个文件，成功替换 {found_count} 个文件。")
                            
                            # 更新UI状态
                            self.replace_button.config(state=tk.NORMAL)
                            self.replace_cancel_button.config(state=tk.DISABLED)
                        else:
                            # 文件名替换完成
                            self.logger.info(f"文件名替换完成。共处理 {processed_files} 个文件，成功替换 {found_count} 个文件名。")
                            self.status_var.set(f"文件名替换完成。共处理 {processed_files} 个文件，成功替换 {found_count} 个文件名。")
                            
                            # 更新UI状态
                            self.filename_replace_button.config(state=tk.NORMAL)
                            self.filename_replace_cancel_button.config(state=tk.DISABLED)
                        
                        # 如果有结果，启用导出按钮
                        if found_count > 0:
                            self.export_button.config(state=tk.NORMAL)
                        else:
                            messagebox.showinfo("操作结果", "未找到需要处理的文件。")
                    elif message_type == 'cancelled':
                        # 根据当前模式处理取消消息
                        if self.mode == 'search':
                            self.logger.info("搜索已取消。")
                            self.status_var.set("搜索已取消。")
                            self.search_button.config(state=tk.NORMAL)
                            self.cancel_button.config(state=tk.DISABLED)
                        elif self.mode == 'replace':
                            self.logger.info("替换已取消。")
                            self.status_var.set("替换已取消。")
                            self.replace_button.config(state=tk.NORMAL)
                            self.replace_cancel_button.config(state=tk.DISABLED)
                        else:
                            self.logger.info("文件名替换已取消。")
                            self.status_var.set("文件名替换已取消。")
                            self.filename_replace_button.config(state=tk.NORMAL)
                            self.filename_replace_cancel_button.config(state=tk.DISABLED)
                        
                    # 标记任务完成
                    self.result_queue.task_done()
                except queue.Empty:
                    # 队列为空，退出循环
                    break
        except Exception as e:
            self.logger.error(f"处理队列消息时发生错误: {str(e)}")
        
        # 继续检查队列，每100毫秒一次
        self.root.after(100, self.process_queue)
    
    def add_result_to_tree(self, result):
        """将搜索结果添加到树状视图中"""
        # 获取修改时间的可读格式
        modified_str = datetime.datetime.fromtimestamp(result['modified_time']).strftime('%Y-%m-%d %H:%M:%S')
        
        # 获取文件大小的可读格式
        size_str = self.get_readable_size(result['size'])
        
        # 添加到树状视图
        self.result_tree.insert("", tk.END, values=(result['name'], result['path'], size_str, modified_str, result['match_type']))
    
    def get_readable_size(self, size_bytes):
        """将字节大小转换为人类可读的格式"""
        if size_bytes < 1024:
            return f"{size_bytes} B"
        elif size_bytes < 1024 * 1024:
            return f"{size_bytes / 1024:.2f} KB"
        elif size_bytes < 1024 * 1024 * 1024:
            return f"{size_bytes / (1024 * 1024):.2f} MB"
        else:
            return f"{size_bytes / (1024 * 1024 * 1024):.2f} GB"
    
    def cancel_search(self):
        """取消搜索过程"""
        self.search_cancelled = True
        self.logger.info("正在取消搜索...")
    
    def sort_results(self):
        """对搜索结果进行排序"""
        if not self.search_results:
            return
        
        sort_by = self.sort_var.get()
        
        # 根据选择的排序方式进行排序
        if sort_by == "文件名":
            sorted_results = sorted(self.search_results, key=lambda x: x['name'].lower())
        elif sort_by == "文件大小":
            sorted_results = sorted(self.search_results, key=lambda x: x['size'], reverse=True)
        elif sort_by == "修改时间":
            sorted_results = sorted(self.search_results, key=lambda x: x['modified_time'], reverse=True)
        
        # 清空结果列表并重新添加排序后的结果
        for item in self.result_tree.get_children():
            self.result_tree.delete(item)
        
        for result in sorted_results:
            self.add_result_to_tree(result)
    
    def show_context_menu(self, event):
        """显示右键菜单"""
        # 选中点击的行
        item = self.result_tree.identify_row(event.y)
        if item:
            self.result_tree.selection_set(item)
            self.result_tree.focus(item)
            # 显示右键菜单
            self.context_menu.post(event.x_root, event.y_root)
    
    def open_file_location(self):
        """打开选中文件的所在文件夹"""
        selected_items = self.result_tree.selection()
        if not selected_items:
            messagebox.showinfo("提示", "请先选择一个文件")
            return
        
        # 获取第一个选中文件的路径
        item = selected_items[0]
        path = self.result_tree.item(item, "values")[1]  # 第2列是文件路径
        
        try:
            # 获取文件所在目录
            directory = os.path.dirname(path)
            
            # 规范化路径（处理反斜杠和路径格式）
            normalized_path = os.path.normpath(path)
            
            self.logger.info(f"尝试打开文件位置: 原始路径={path}, 规范化路径={normalized_path}")
            
            # 在Windows上打开目录并选中文件
            if os.name == 'nt':  # Windows系统
                # 使用带引号的命令格式，这在我们的测试中也成功了
                cmd = f'explorer /select,"{normalized_path}"'  # 逗号后有空格，路径带引号
                self.logger.info(f"执行命令: {cmd}")
                subprocess.Popen(cmd, shell=True)
            else:
                # 其他系统仅打开目录
                subprocess.Popen(['xdg-open', directory])
            
            self.logger.info(f"已打开文件位置: {directory}")
        except Exception as e:
            error_msg = f"无法打开文件位置: {str(e)}"
            messagebox.showerror("错误", error_msg)
            self.logger.error(error_msg)
            self.logger.error(f"路径信息: 原始路径={path}, 规范化路径={normalized_path if 'normalized_path' in locals() else '未计算'}")
    
    def ask_export_format(self):
        """询问导出格式"""
        # 创建导出格式选择对话框
        export_dialog = tk.Toplevel(self.root)
        export_dialog.title("导出搜索结果")
        export_dialog.geometry("350x200")
        export_dialog.resizable(False, False)
        export_dialog.transient(self.root)
        export_dialog.grab_set()
        
        # 居中显示
        export_dialog.update_idletasks()
        width = export_dialog.winfo_width()
        height = export_dialog.winfo_height()
        x = (self.root.winfo_width() // 2) + self.root.winfo_x() - (width // 2)
        y = (self.root.winfo_height() // 2) + self.root.winfo_y() - (height // 2)
        export_dialog.geometry(f"{width}x{height}+{x}+{y}")
        
        # 创建选择项
        frame = ttk.Frame(export_dialog, padding="20")
        frame.pack(fill=tk.BOTH, expand=True)
        
        ttk.Label(frame, text="请选择导出格式:", font=self.default_font).pack(anchor=tk.W, pady=(0, 10))
        
        format_var = tk.StringVar(value="CSV")
        
        ttk.Radiobutton(frame, text="CSV格式 (*.csv)", variable=format_var, value="CSV").pack(anchor=tk.W, pady=5)
        ttk.Radiobutton(frame, text="文本格式 (*.txt)", variable=format_var, value="TXT").pack(anchor=tk.W, pady=5)
        
        # 按钮区域
        button_frame = ttk.Frame(frame)
        button_frame.pack(fill=tk.X, pady=(10, 0))
        
        ttk.Button(button_frame, text="确定", command=lambda: (
            self.export_results(format_var.get().lower()),
            export_dialog.destroy()
        )).pack(side=tk.RIGHT, padx=(0, 10))
        
        ttk.Button(button_frame, text="取消", command=export_dialog.destroy).pack(side=tk.RIGHT)
    
    def export_results(self, export_format='csv'):
        """导出搜索结果"""
        if not self.search_results:
            messagebox.showinfo("提示", "没有可导出的搜索结果")
            return
        
        # 确定文件扩展名
        if export_format == 'csv':
            file_ext = '.csv'
            file_types = [("CSV文件", "*.csv")]
        elif export_format == 'txt':
            file_ext = '.txt'
            file_types = [("文本文件", "*.txt")]
        else:
            messagebox.showerror("错误", f"不支持的导出格式: {export_format}")
            return
        
        # 打开文件保存对话框
        current_time = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
        default_file = f"搜索结果_{current_time}{file_ext}"
        
        file_path = filedialog.asksaveasfilename(
            defaultextension=file_ext,
            filetypes=file_types,
            initialfile=default_file,
            title="保存搜索结果"
        )
        
        if not file_path:
            return  # 用户取消了操作
        
        try:
            # 按当前排序方式排序结果
            sort_by = self.sort_var.get()
            if sort_by == "文件名":
                sorted_results = sorted(self.search_results, key=lambda x: x['name'].lower())
            elif sort_by == "文件大小":
                sorted_results = sorted(self.search_results, key=lambda x: x['size'], reverse=True)
            elif sort_by == "修改时间":
                sorted_results = sorted(self.search_results, key=lambda x: x['modified_time'], reverse=True)
            
            if export_format == 'csv':
                self.export_to_csv(sorted_results, file_path)
            else:
                self.export_to_txt(sorted_results, file_path)
                
            self.logger.info(f"搜索结果已成功导出到 {file_path}")
            messagebox.showinfo("成功", f"搜索结果已成功导出到:\n{file_path}")
        except Exception as e:
            error_msg = f"导出失败: {str(e)}"
            messagebox.showerror("错误", error_msg)
            self.logger.error(error_msg)
    
    def export_to_csv(self, results, file_path):
        """导出搜索结果到CSV文件"""
        with open(file_path, 'w', newline='', encoding='utf-8-sig') as csvfile:
            fieldnames = ['序号', '文件名', '文件路径', '匹配类型', '大小', '修改时间']
            writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
            
            # 写入表头
            writer.writeheader()
            
            # 写入数据
            for i, result in enumerate(results, 1):
                modified_str = datetime.datetime.fromtimestamp(result['modified_time']).strftime('%Y-%m-%d %H:%M:%S')
                size_str = self.get_readable_size(result['size'])
                
                writer.writerow({
                    '序号': i,
                    '文件名': result['name'],
                    '文件路径': result['path'],
                    '匹配类型': result['match_type'],
                    '大小': size_str,
                    '修改时间': modified_str
                })
    
    def export_to_txt(self, results, file_path):
        """导出搜索结果到文本文件"""
        with open(file_path, 'w', encoding='utf-8') as txtfile:
            # 写入导出信息
            txtfile.write(f"文档内容搜索工具 - 搜索结果导出\n")
            txtfile.write(f"导出时间: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
            txtfile.write(f"搜索目录: {self.directory}\n")
            txtfile.write(f"搜索关键字: {self.keyword}\n")
            txtfile.write(f"结果数量: {len(results)}\n")
            txtfile.write(f"排序方式: {self.sort_var.get()}\n")
            txtfile.write("=" * 80 + "\n\n")
            
            # 写入结果数据
            for i, result in enumerate(results, 1):
                modified_str = datetime.datetime.fromtimestamp(result['modified_time']).strftime('%Y-%m-%d %H:%M:%S')
                size_str = self.get_readable_size(result['size'])
                
                txtfile.write(f"{i}. 文件名: {result['name']}\n")
                txtfile.write(f"   路径: {result['path']}\n")
                txtfile.write(f"   匹配: {result['match_type']}\n")
                txtfile.write(f"   大小: {size_str}\n")
                txtfile.write(f"   修改时间: {modified_str}\n")
                txtfile.write("-" * 80 + "\n")
                
    # 以下是替换相关的方法
    def start_replace(self):
        """开始替换过程"""
        # 获取替换参数
        self.replace_directory = self.replace_directory_var.get()
        self.find_text = self.find_text_var.get()
        self.replace_text = self.replace_text_var.get()
        
        # 验证参数
        if not self.replace_directory or not os.path.isdir(self.replace_directory):
            messagebox.showerror("错误", "请选择有效的替换目录")
            return
        
        if not self.find_text:
            messagebox.showerror("错误", "请输入要查找的文本")
            return
        
        # 询问用户是否确认替换
        confirm_msg = f"确定要将目录 '{self.replace_directory}' 及其子目录下所有 "
        confirm_msg += f"{', '.join(self.replace_file_types)} 文件中的 \"{self.find_text}\" 替换为 \"{self.replace_text}\" 吗？\n\n"
        confirm_msg += "此操作将直接修改原文件，建议先备份重要文件！"
        
        if not messagebox.askyesno("确认替换", confirm_msg):
            return
        
        # 重置替换状态
        self.replace_cancelled = False
        self.search_results = []
        
        # 清空结果列表
        for item in self.result_tree.get_children():
            self.result_tree.delete(item)
        
        # 更新UI状态
        self.replace_button.config(state=tk.DISABLED)
        self.replace_cancel_button.config(state=tk.NORMAL)
        self.export_button.config(state=tk.DISABLED)
        
        # 记录开始信息
        self.logger.info(f"开始替换 - 目录: {self.replace_directory}, 查找: '{self.find_text}', 替换为: '{self.replace_text}'")
        self.status_var.set("正在初始化替换...")
        
        # 创建并启动替换线程
        self.replace_thread = threading.Thread(target=self.replace_in_thread)
        self.replace_thread.daemon = True
        self.replace_thread.start()
    
    def replace_in_thread(self):
        """在后台线程中执行替换"""
        try:
            # 发送初始状态
            self.result_queue.put(('status', "正在计算文件总数..."))
            
            # 计算总文件数
            total_files = 0
            target_files = []
            
            for root, _, files in os.walk(self.replace_directory):
                for file in files:
                    _, ext = os.path.splitext(file)
                    if ext.lower() in self.replace_file_types:
                        total_files += 1
                        target_files.append(os.path.join(root, file))
                if self.replace_cancelled:
                    self.result_queue.put(('cancelled', None))
                    return
            
            self.logger.debug(f"总共找到 {total_files} 个匹配的文件")
            
            # 执行替换
            processed_files = 0
            replaced_count = 0
            
            for file_path in target_files:
                if self.replace_cancelled:
                    self.result_queue.put(('cancelled', None))
                    return
                    
                processed_files += 1
                
                # 发送状态更新
                progress = f"{processed_files}/{total_files} ({processed_files/total_files:.0%})"
                file_name = os.path.basename(file_path)
                self.result_queue.put(('status', f"替换中... {progress} - {file_name}"))
                
                try:
                    # 执行文本替换
                    replaced = self.replace_text_in_docx(file_path, self.find_text, self.replace_text)
                    
                    if replaced:
                        replaced_count += 1
                        
                        # 获取文件信息
                        file_size = os.path.getsize(file_path)
                        modified_time = os.path.getmtime(file_path)
                        result = {
                            'name': file_name,
                            'path': file_path,
                            'size': file_size,
                            'modified_time': modified_time,
                            'match_type': '已替换'
                        }
                        self.search_results.append(result)
                        self.result_queue.put(('result', result))
                        self.logger.debug(f"已替换文件: {file_path}")
                except Exception as e:
                    error_msg = f"处理文件 {file_path} 时出错: {str(e)}"
                    self.result_queue.put(('error', error_msg))
                    self.logger.warning(error_msg)
            
            # 替换完成
            self.result_queue.put(('finished', (replaced_count, processed_files)))
        except Exception as e:
            self.logger.error(f"替换过程中发生错误: {str(e)}")
            self.result_queue.put(('error', str(e)))
    
    def replace_text_in_docx(self, file_path, old_text, new_text):
        """替换docx文件中的文本内容
        返回值: True表示成功替换，False表示文件中未找到要替换的文本
        """
        if not has_docx:
            raise Exception("缺少python-docx库，无法处理docx文件")
            
        doc = Document(file_path)
        found = False
        
        # 替换段落中的文本
        for para in doc.paragraphs:
            if old_text in para.text:
                found = True
                self.replace_text_in_paragraph(para, old_text, new_text)
                # 单独处理巡检报告中第一行字符被拆分问题
                self.replace_first_text_in_paragraph(para, old_text, new_text)
        
        # 替换表格中的文本
        for table in doc.tables:
            for row in table.rows:
                for cell in row.cells:
                    for para in cell.paragraphs:
                        if old_text in para.text:
                            found = True
                            self.replace_text_in_paragraph(para, old_text, new_text)
                            self.replace_first_text_in_paragraph(para, old_text, new_text)
        
        # 如果找到了要替换的文本，则保存文件
        if found:
            # 直接覆盖原文件
            doc.save(file_path)
            
        return found
    
    def replace_first_text_in_paragraph(self, paragraph, old_text, new_text):
        """处理段落中第一行字符被拆分的问题"""
        inline = paragraph.runs
        first_five_text = ""
        
        # 单独处理巡检报告中第一行字符被拆分问题
        for i in range(min(5, len(inline))):
            first_five_text += inline[i].text
            
        if old_text in first_five_text:
            for i in range(min(5, len(inline))):
                inline[i].text = ''
            inline[min(5, len(inline)) - 1].add_text(new_text)
    
    def replace_text_in_paragraph(self, paragraph, old_text, new_text):
        """替换段落中的文本"""
        if old_text in paragraph.text:
            inline = paragraph.runs
            for i in range(len(inline)):
                if old_text in inline[i].text:
                    text = inline[i].text.replace(old_text, new_text)
                    inline[i].text = text
    
    def cancel_replace(self):
        """取消替换过程"""
        self.replace_cancelled = True
        self.logger.info("正在取消替换...")
        
    def start_filename_replace(self):
        """开始文件名替换过程"""
        # 获取文件名替换参数
        self.filename_replace_directory = self.filename_replace_directory_var.get()
        self.filename_find_text = self.filename_find_text_var.get()
        self.filename_replace_text = self.filename_replace_text_var.get()
        
        # 验证参数
        if not self.filename_replace_directory or not os.path.isdir(self.filename_replace_directory):
            messagebox.showerror("错误", "请选择有效的文件名替换目录")
            return
        
        if not self.filename_find_text:
            messagebox.showerror("错误", "请输入要查找的文本")
            return
        
        # 询问用户是否确认替换
        confirm_msg = f"确定要将目录 '{self.filename_replace_directory}' 及其子目录下所有文件名中的 "
        confirm_msg += f'"{self.filename_find_text}" 替换为 "{self.filename_replace_text}" 吗？\n\n'
        confirm_msg += "此操作将直接修改文件名，建议先备份重要文件！"
        
        if not messagebox.askyesno("确认文件名替换", confirm_msg):
            return
        
        # 重置替换状态
        self.filename_replace_cancelled = False
        self.search_results = []
        
        # 清空结果列表
        for item in self.result_tree.get_children():
            self.result_tree.delete(item)
        
        # 更新UI状态
        self.filename_replace_button.config(state=tk.DISABLED)
        self.filename_replace_cancel_button.config(state=tk.NORMAL)
        self.export_button.config(state=tk.DISABLED)
        
        # 记录开始信息
        self.logger.info(f"开始文件名替换 - 目录: {self.filename_replace_directory}, 查找: '{self.filename_find_text}', 替换为: '{self.filename_replace_text}'")
        self.status_var.set("正在初始化文件名替换...")
        
        # 创建并启动替换线程
        self.filename_replace_thread = threading.Thread(target=self.filename_replace_in_thread)
        self.filename_replace_thread.daemon = True
        self.filename_replace_thread.start()
    
    def filename_replace_in_thread(self):
        """在后台线程中执行文件名替换"""
        try:
            # 发送初始状态
            self.result_queue.put(("status", "正在计算文件总数..."))
            
            # 计算总文件数和需要替换的文件
            total_files = 0
            target_files = []
            
            for root, _, files in os.walk(self.filename_replace_directory):
                for file in files:
                    total_files += 1
                    if self.filename_find_text in file:
                        target_files.append((root, file))
                if self.filename_replace_cancelled:
                    self.result_queue.put(("cancelled", None))
                    return
            
            self.logger.debug(f"总共找到 {total_files} 个文件，其中 {len(target_files)} 个文件包含需要替换的文本")
            
            # 执行文件名替换
            processed_files = 0
            replaced_count = 0
            
            for root, file in target_files:
                if self.filename_replace_cancelled:
                    self.result_queue.put(("cancelled", None))
                    return
                     
                processed_files += 1
                
                # 发送状态更新
                progress = f"{processed_files}/{len(target_files)} ({processed_files/len(target_files):.0%})"
                self.result_queue.put(("status", f"文件名替换中... {progress} - {file}"))
                
                try:
                    # 构造新文件名
                    new_file = file.replace(self.filename_find_text, self.filename_replace_text)
                    
                    # 构造完整路径
                    old_path = os.path.join(root, file)
                    new_path = os.path.join(root, new_file)
                    
                    # 执行文件重命名
                    os.rename(old_path, new_path)
                    replaced_count += 1
                    
                    # 获取文件信息并添加到结果
                    file_size = os.path.getsize(new_path)
                    modified_time = os.path.getmtime(new_path)
                    result = {
                        'name': new_file,
                        'path': new_path,
                        'size': file_size,
                        'modified_time': modified_time,
                        'match_type': '文件名已替换'
                    }
                    self.search_results.append(result)
                    self.result_queue.put(("result", result))
                    self.logger.debug(f"已重命名文件: {old_path} -> {new_path}")
                except Exception as e:
                    error_msg = f"处理文件 {file} 时出错: {str(e)}"
                    self.result_queue.put(("error", error_msg))
                    self.logger.warning(error_msg)
            
            # 替换完成
            self.result_queue.put(("finished", (replaced_count, len(target_files))))
        except Exception as e:
            self.logger.error(f"文件名替换过程中发生错误: {str(e)}")
            self.result_queue.put(("error", str(e)))
    
    def cancel_filename_replace(self):
        """取消文件名替换过程"""
        self.filename_replace_cancelled = True
        self.logger.info("正在取消文件名替换...")


def main():
    """主函数"""
    root = tk.Tk()
    app = DocSearchGUI(root)
    root.mainloop()


if __name__ == "__main__":
    main()